package bancosys.tec.services.implementor;

import java.util.List;

import jmine.tec.di.scope.TypedScope;
import jmine.tec.di.type.TypeConvertionException;
import jmine.tec.services.ServicesController;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServiceValidationExecutionException;
import jmine.tec.services.api.ServicesPage;

import org.springframework.transaction.TransactionDefinition;

import bancosys.tec.exception.AbstractException;
import bancosys.tec.persist.controller.PersistenceEnabledController;

/**
 * Proxy que faz a ponte entre um servico implementado com anotacoes e o sistema de servicos habitual.
 */
public final class ServiceImplementorProxy implements Service<PersistenceEnabledController> {

    private final ServiceImplementorAccessor accessor;

    private TypedScope serviceProperties;

    private ServicesController servicesController;

    private ServicesPage servicesPage;

    private String serviceLocation;

    /**
     * C'tor
     * 
     * @param accessor o {@link ServiceImplementorAccessor}
     */
    public ServiceImplementorProxy(ServiceImplementorAccessor accessor) {
        this.accessor = accessor;
    }

    private Object targetService;

    /**
     * Devolve o objeto, já com as suas dependências, caso seja possível construí-lo
     * 
     * @return Object
     */
    private Object getTargetService() {
        if (this.targetService == null) {
            try {
                this.targetService = this.accessor.createWithDependencies(this.getServiceProperties());
            } catch (TypeConvertionException e) {
                throw new ServiceValidationExecutionException(e.getLocalizedMessageHolder());
            }
        }
        return this.targetService;
    }

    /**
     * executa o servico
     * 
     * @throws ServiceExecutionException e
     */
    public void execute() throws ServiceExecutionException {
        try {
            this.accessor.validateBeforeExecution(this.getTargetService(), this.getServiceProperties());
            this.accessor.executeAndFillScope(this.getTargetService(), this.getServiceProperties());
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (ServiceExecutionException e) {
            throw e;
        } catch (AbstractException e) {
            throw new ServiceExecutionException(e.getLocalizedMessageHolder(), e);
        } catch (Exception e) {
            throw new ServiceExecutionException(e);
        } finally {
            this.targetService = null;
        }
    }

    /**
     * @return {@link ActionsEnum}
     */
    public ActionsEnum getAction() {
        return this.accessor.getActionType();
    }

    /**
     * @return String
     */
    public String getServiceName() {
        return this.accessor.getServiceName();
    }

    /**
     * @return the serviceLocation
     */
    public String getServiceLocation() {
        return this.serviceLocation;
    }

    /**
     * @param serviceLocation the serviceLocation to set
     */
    public void setServiceLocation(String serviceLocation) {
        this.serviceLocation = serviceLocation;
    }

    /**
     * @return {@link ServiceImplementorProxy}
     */
    @Override
    public ServiceImplementorProxy clone() {
        return new ServiceImplementorProxy(this.accessor);
    }

    /**
     * Nao devolve nada. Este método só está aqui para conformar com a interface.
     * 
     * @return <code>null</code>
     */
    public PersistenceEnabledController getController() {
        // ignore
        return null;
    }

    /**
     * Nao devolve nada. Este método só está aqui para conformar com a interface.
     * 
     * @return <code>null</code>
     */
    public Class<PersistenceEnabledController> getControllerClass() {
        return null;
    }

    /**
     * Não guarda o controller. Este método só está aqui para conformar com a interface.
     * 
     * @param controller controller
     */
    public void setController(PersistenceEnabledController controller) {
        // ignore
    }

    /**
     * Valida todas as restrições
     * 
     * @return List
     */
    public List<String> validate() {
        return this.accessor.validateStaticState(this.getServiceProperties());
    }

    /**
     * @return the serviceProperties
     */
    public TypedScope getServiceProperties() {
        return this.serviceProperties;
    }

    /**
     * @param serviceProperties the serviceProperties to set
     */
    public void setServiceProperties(TypedScope serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    /**
     * @return the servicesController
     */
    public ServicesController getServicesController() {
        return this.servicesController;
    }

    /**
     * @param servicesController the servicesController to set
     */
    public void setServicesController(ServicesController servicesController) {
        this.servicesController = servicesController;
    }

    /**
     * @return the servicesPage
     */
    public ServicesPage getServicesPage() {
        return this.servicesPage;
    }

    /**
     * @param servicesPage the servicesPage to set
     */
    public void setServicesPage(ServicesPage servicesPage) {
        this.servicesPage = servicesPage;
    }

    /**
     * Implementação de toString com saída idêntica à saída gerada por AbstractService#toString() com uma marca adicional indicando que se
     * trata de um ServiceImplementorProxy.
     * 
     * @return String
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        sb.append("[");

        if (this.getServiceLocation() != null) {
            sb.append(this.getServiceLocation());
            sb.append(":");
        } else if (this.getServicesPage() != null) {
            sb.append(this.getServicesPage().getName());
            sb.append(":");
        }

        sb.append(this.getAction());
        sb.append(".");
        sb.append(this.getServiceName());

        sb.append("] ");

        sb.append(this.getServiceProperties().toString());

        return sb.toString();
    }

    /**
     * Expoe o {@link ServiceImplementorAccessor} usado por este proxy
     * 
     * @return {@link ServiceImplementorAccessor}
     */
    public ServiceImplementorAccessor getAccessor() {
        return this.accessor;
    }

    /**
     * {@inheritDoc}
     */
    public TransactionDefinition getTransactionDefinition() {
        return this.accessor.getTransactionDefinition();
    }

}
